use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use std::io;

use futures::StreamExt;
use libp2p::{
    gossipsub, mdns, noise,
    swarm::{NetworkBehaviour, SwarmEvent},
    tcp, yamux,
};
use tokio::sync::mpsc::{Receiver, Sender};
use tokio::time::Duration;

/// Network events sent from backend to frontend
#[derive(Debug)]
pub enum NetworkEvent {
    MessageReceived(String),
    PeerConnected(String),
    PeerDisconnected(String),
}

/// Commands sent from frontend to backend
#[derive(Debug)]
pub enum NetworkCommand {
    SendMessage(String),
    SubscribeTopic(String),
    UnsubscribeTopic(String),
}

/// Custom network behaviour combining Gossipsub and Mdns
#[derive(NetworkBehaviour)]
struct Libp2pMessagingBehaviour {
    gossipsub: gossipsub::Behaviour,
    mdns: mdns::tokio::Behaviour,
}

pub struct Libp2pMessaging {
    swarm: libp2p::Swarm<Libp2pMessagingBehaviour>,
    command_receiver: Receiver<NetworkCommand>,
    event_sender: Sender<NetworkEvent>,
}

impl Libp2pMessaging {
    pub fn new(
        command_receiver: Receiver<NetworkCommand>,
        event_sender: Sender<NetworkEvent>,
    ) -> Result<Self, Box<dyn std::error::Error>> {
        let mut swarm = libp2p::SwarmBuilder::with_new_identity()
            .with_tokio()
            .with_tcp(
                tcp::Config::default(),
                noise::Config::new,
                yamux::Config::default,
            )?
            .with_quic()
            .with_behaviour(|key| {
                // Message ID function
                let message_id_fn = |message: &gossipsub::Message| {
                    let mut s = DefaultHasher::new();
                    message.data.hash(&mut s);
                    gossipsub::MessageId::from(s.finish().to_string())
                };

                // Gossipsub config
                let gossipsub_config = gossipsub::ConfigBuilder::default()
                    .heartbeat_interval(Duration::from_secs(10))
                    .validation_mode(gossipsub::ValidationMode::Strict)
                    .message_id_fn(message_id_fn)
                    .build()
                    .map_err(io::Error::other)?;

                let gossipsub = gossipsub::Behaviour::new(
                    gossipsub::MessageAuthenticity::Signed(key.clone()),
                    gossipsub_config,
                )?;

                let mdns = mdns::tokio::Behaviour::new(
                    mdns::Config::default(),
                    key.public().to_peer_id(),
                )?;

                Ok(Libp2pMessagingBehaviour { gossipsub, mdns })
            })?
            .build();

        Ok(Self {
            swarm,
            command_receiver,
            event_sender,
        })
    }

    pub async fn run(&mut self) {
        // Listen on all interfaces
        self.swarm
            .listen_on("/ip4/0.0.0.0/tcp/0".parse().unwrap())
            .unwrap();

        // Subscribe to default topic
        let chat_topic = gossipsub::IdentTopic::new("chat");
        self.swarm
            .behaviour_mut()
            .gossipsub
            .subscribe(&chat_topic)
            .unwrap();

        println!("Libp2p messaging started, waiting for events...");

        loop {
            tokio::select! {
                command = self.command_receiver.recv() => {
                    if let Some(command) = command {
                        match command {
                            NetworkCommand::SendMessage(msg) => {
                                let chat_topic = gossipsub::IdentTopic::new("chat");
                                if let Err(e) = self.swarm
                                    .behaviour_mut()
                                    .gossipsub
                                    .publish(chat_topic, msg.as_bytes())
                                {
                                    eprintln!("Failed to send message: {}", e);
                                }
                            }
                            NetworkCommand::SubscribeTopic(topic) => {
                                let topic = gossipsub::IdentTopic::new(topic);
                                if let Err(e) = self.swarm.behaviour_mut().gossipsub.subscribe(&topic) {
                                    eprintln!("Failed to subscribe to topic: {}", e);
                                }
                            }
                            NetworkCommand::UnsubscribeTopic(topic) => {
                                let topic = gossipsub::IdentTopic::new(topic);
                                if !self.swarm.behaviour_mut().gossipsub.unsubscribe(&topic) {
                                    eprintln!("Failed to unsubscribe from topic: {}", topic);
                                }
                            }
                        }
                    }
                }
                event = self.swarm.select_next_some() => {
                    match event {
                        SwarmEvent::Behaviour(Libp2pMessagingBehaviourEvent::Gossipsub(gossipsub::Event::Message { message, .. })) => {
                            if let Err(e) = self.event_sender
                                .send(NetworkEvent::MessageReceived(
                                    String::from_utf8_lossy(&message.data).to_string(),
                                ))
                                .await
                            {
                                eprintln!("Failed to send message event: {}", e);
                            }
                        }
                        SwarmEvent::Behaviour(Libp2pMessagingBehaviourEvent::Mdns(mdns::Event::Discovered(list))) => {
                            for (peer_id, _multiaddr) in list {
                                println!("mDNS discovered a new peer: {peer_id}");
                                self.swarm.behaviour_mut().gossipsub.add_explicit_peer(&peer_id);

                                if let Err(e) = self.event_sender
                                    .send(NetworkEvent::PeerConnected(peer_id.to_string()))
                                    .await
                                {
                                    eprintln!("Failed to send peer connected event: {}", e);
                                }
                            }
                        }
                        SwarmEvent::Behaviour(Libp2pMessagingBehaviourEvent::Mdns(mdns::Event::Expired(list))) => {
                            for (peer_id, _multiaddr) in list {
                                println!("mDNS discover peer has expired: {peer_id}");
                                self.swarm
                                    .behaviour_mut()
                                    .gossipsub
                                    .remove_explicit_peer(&peer_id);

                                if let Err(e) = self.event_sender
                                    .send(NetworkEvent::PeerDisconnected(peer_id.to_string()))
                                    .await
                                {
                                    eprintln!("Failed to send peer disconnected event: {}", e);
                                }
                            }
                        }
                        SwarmEvent::NewListenAddr { address, .. } => {
                            println!("Listening on address: {}", address);
                        }
                        _ => {
                            eprintln!("Unhandled swarm event");
                        }
                    }
                }
            }
            tokio::time::sleep(Duration::from_millis(200)).await;
        }
    }
}
